#include "polygon_triangulation.h"

namespace Packing_common
{
	void Polygon_triangulation::insert(const Packing_polygon& polygon, int group_no, int sample_nb)
	{
		Polygon_sampler ps(polygon);
		std::vector<Point_2> smpl_pnts;
		ps.edge_sample(sample_nb, smpl_pnts);
		for (size_t i = 0; i < smpl_pnts.size(); i++)
		{
			Vertex_handle vh = cdt.insert(smpl_pnts[i]);
			vh->group_id = group_no;
		}
		if (group_no >= 0) // only count the points that are not on the container
			nb_groups++;
	}
	void Polygon_triangulation::end_insert()
	{
		// we do the object truncation on purpose because only the Delaunay triangulation is only used for initialization
		group_vertices();
		compute_CAT();
		open = false;
	}
	void Polygon_triangulation::compute_CAT()
	{
		cat_cells.clear();
		cat_cells.resize(nb_groups);
		//std::vector< std::list<Vertex_handle> > vert_group(nb_groups);
		//for (Triangulation_2::Vertex_iterator vit = cdt.vertices_begin(); vit != cdt.vertices_end(); ++vit)
		//{
		//	if (vit->group_id >= 0 && vit->group_id < nb_groups)
		//		vert_group[vit->group_id].push_back(vit);
		//}
		for (size_t i = 0; i < vert_groups.size(); i++)
		{
			cat_cells[i].clear();
			for (std::list<Vertex_handle>::const_iterator it = vert_groups[i].begin(); it != vert_groups[i].end(); ++it)
			{
				Triangulation_2::Face_circulator fc_start = cdt.incident_faces(*it), fc_end;
				fc_end = fc_start;
				do
				{
					Vertex_handle v[3] = {fc_start->vertex(0), fc_start->vertex(1), fc_start->vertex(2)};
					if (v[0]->group_id == v[1]->group_id && v[1]->group_id == v[2]->group_id)
						break;
					++fc_start;
				} while (fc_start != fc_end);
				fc_end = fc_start;
				bool seq_start = true;
				do
				{
					if (cdt.is_infinite(fc_start))
					{
						++fc_start;
						continue;
					}
					int idx = fc_start->index(*it);
					Vertex_handle cw_vert = fc_start->vertex(fc_start->cw(idx)), ccw_vert = fc_start->vertex(fc_start->ccw(idx));
					if (seq_start && (*it)->group_id != ccw_vert->group_id)
					{
						seq_start = false;
						cat_cells[i].push_back( CGAL::midpoint((*it)->point(), ccw_vert->point()) );
					}
					if (seq_start && (*it)->group_id != cw_vert->group_id && (*it)->group_id == ccw_vert->group_id)
					{
						seq_start = false;
						cat_cells[i].push_back( CGAL::midpoint(ccw_vert->point(), cw_vert->point()) );
						cat_cells[i].push_back( CGAL::midpoint((*it)->point(), cw_vert->point()) );
					}
					if ((*it)->group_id != cw_vert->group_id && cw_vert->group_id != ccw_vert->group_id && ccw_vert->group_id != (*it)->group_id)
					{
						cat_cells[i].push_back( CGAL::centroid((*it)->point(), cw_vert->point(), ccw_vert->point()) );
						cat_cells[i].push_back( CGAL::midpoint((*it)->point(), cw_vert->point()) );
					}
					else if ((*it)->group_id != cw_vert->group_id)
					{
						cat_cells[i].push_back( CGAL::midpoint((*it)->point(), cw_vert->point()) );
					}
					++fc_start;
				} while (fc_start != fc_end);
			}
		}

		polygonize_CAT();
	}

	void Polygon_triangulation::polygonize_CAT()
	{
		polygonal_cat_cells.clear();
		polygonal_cat_cells.reserve(cat_cells.size());
		std::function<size_t(const Point_2&)> point_hash = [](const Point_2& p) 
															 { 
																 std::hash<double> h;
																 return h(p.x()) ^ h(p.y());
															 };
		std::function<bool(const Point_2&, const Point_2&)> point_eqal = [](const Point_2& p0, const Point_2& p1)
																		{
																			return p0.x() == p1.x() && p0.y() == p1.y();
																		};

		for (size_t i = 0; i < cat_cells.size(); i++)
		{
			std::list<Point_2> verts;
			std::unordered_set< Point_2, 
								std::function<size_t(const Point_2&)>, 
								std::function<bool(const Point_2&, const Point_2&)> 
							  > m(cat_cells[i].size(), point_hash, point_eqal); 
			for (std::list<Point_2>::const_iterator it = cat_cells[i].begin(); it != cat_cells[i].end(); ++it)
			{
				if (m.find(*it) == m.end())
				{
					verts.push_back(*it);
					m.insert(*it);
				}
			}
			polygonal_cat_cells.push_back(CGAL_polygon(verts.begin(), verts.end()));
			//std::cout<<"CAT vertex list size: "<<cat_cells[i].size()<<std::endl;
			//std::cout<<"CAT polygon size: "<<polygonal_cat_cells.back().size()<<std::endl;
		}
	}

	void Polygon_triangulation::draw_triangulation() const
	{
		glColor3f(.0f, .0f, .6f);
		glBegin(GL_LINES);
		for (Triangulation_2::Finite_edges_iterator eit = cdt.finite_edges_begin(); eit != cdt.finite_edges_end(); ++eit)
		{
			Face_handle f = eit->first;
			int i = eit->second;
			Vertex_handle src = f->vertex(f->ccw(i)), tgt = f->vertex(f->cw(i));
			if (src->group_id < 0 && tgt->group_id < 0)	
				continue;
			glVertex2d(src->point().x(), src->point().y());
			glVertex2d(tgt->point().x(), tgt->point().y());	
			
		}
		glEnd();
	}

	void Polygon_triangulation::draw_CAT() const
	{
		glColor3f(0.75f, 0.0f, 0.0f);
		glLineWidth(2.0f);
		for (size_t i = 0; i < nb_groups; i++)
		{
			glBegin(GL_LINE_STRIP);
			for (std::list<Point_2>::const_iterator it = cat_cells[i].begin(); it != cat_cells[i].end(); ++it)
				glVertex2d(it->x(), it->y());
			glEnd();
		}
	}
	void Polygon_triangulation::draw_polygonal_CAT() const
	{
		for (size_t i = 0; i < polygonal_cat_cells.size(); i++)
		{
			glBegin(GL_LINE_LOOP);
			for (CGAL_polygon::Vertex_const_iterator vit = polygonal_cat_cells[i].vertices_begin(); vit != polygonal_cat_cells[i].vertices_end(); ++vit)
				glVertex2d(vit->x(), vit->y());
			glEnd();
		}
	}
	void Polygon_triangulation::group_vertices()
	{
		vert_groups.clear();
		bnd_verts.clear();
		vert_groups.resize(nb_groups);
		for (Triangulation_2::Finite_vertices_iterator vit = cdt.finite_vertices_begin(); vit != cdt.finite_vertices_end(); ++vit)
		{
			if (vit->group_id >= 0)
				vert_groups[vit->group_id].push_back(vit);
			else
				bnd_verts.push_back(vit);
		}
	}
	void Polygon_triangulation::update_triangulation()
	{
		Triangulation_2 original_cdt;
		// back up old triangulation for the information about constraint edges 
		std::map<Vertex_handle, Vertex_handle> vert_map;
		cdt.swap(original_cdt);
		//std::list<Vertex_handle> constrain_edges;
		for (Triangulation_2::All_vertices_iterator vit = original_cdt.all_vertices_begin(); vit != original_cdt.all_vertices_end(); ++vit)
		{
			if (original_cdt.is_infinite(vit) || vit->group_id == -2)	continue;
			Vertex_handle v = cdt.insert(vit->point());
			v->group_id = vit->group_id;
			vert_map[vit] = v;
		}
		// specify the constraint edges
		for (Triangulation_2::Finite_edges_iterator eit = original_cdt.finite_edges_begin(); eit != original_cdt.finite_edges_end(); ++eit)
		{
			Face_handle f = eit->first;
			int i = eit->second;
			Vertex_handle v0 = f->vertex(f->cw(i)), v1 = f->vertex(f->ccw(i));
			if (v0->group_id == v1->group_id && v0->group_id >= 0)
				cdt.insert_constraint(vert_map[v0], vert_map[v1]);
		}
		group_vertices();
		compute_CAT();
	}
}
